package fortunata;

import com.ecyrd.jspwiki.WikiContext;
import com.ecyrd.jspwiki.plugin.PluginException;
import com.hp.hpl.jena.ontology.*;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.util.FileManager;
import org.apache.log4j.Logger;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * This class hides the publication mechanism of the semantic data. In this case it has been implemented as
 * a file. It do fine for small models, with a few hundreds of instances. Each time a new data is added, the file
 * is loaded in memory, updated, and written on disk again. For higher performance you can use a "always in memory"
 * model and serialize it (write it to disk) each time an update is commited.
 * The semantic data is stored in two different files: definitions (ontology model with no instances) and instances.
 * When you add new instances to the model, the definitions model is not affected, just the instances file.
 */
public abstract class FortunataSWApplication {

    String ontoFileName;
    String individualsFileName;
    String URL;
    String URI;
    String aliasOfns;
    String mainClassName;
    String ontologyName;
    private static Logger log = Logger.getLogger (FortunataSWApplication.class);
    private static final String serializationStyle = "RDF/XML-ABBREV";
    private OntModelSpec modelSpec = OntModelSpec.OWL_MEM_RDFS_INF;

    /**
     * This method adds the basic information about any SemWebApp
     * @param ontoFileName  The name of the file that will store the ontology's definitions. e.g. "test.model.owl"
     * @param individualsFileName The name of the file that will store the ontology's instances. e.g. "test.data.owl"
     * @param URL Will be used as a base for all the ontology's elements. It is not mandatory to be a real (existing) URL.
     * @param alias The alias of the namespace used by the ontology. It cannot have white spaces.
     * @param mainClassName The name that will have the individuals class.
     * @param ontologyName  The name that will have the ontology.
     */
    public void initialize(String ontoFileName,
                           String individualsFileName,
                           String URL,
                           String alias,
                           String mainClassName,
                           String ontologyName){
       this.ontoFileName = ontoFileName;
       this.individualsFileName = individualsFileName;
       this.URL = URL;
       this.URI = URL + "#";
       this.aliasOfns = alias;
       this.mainClassName = mainClassName;
       this.ontologyName = ontologyName;
    }
    public String getOntoFileName() {return ontoFileName;}
    public String getIndividualsFileName() {return individualsFileName;}
    public String getURL() {return URL;}
    public String getURI() {return URI;}
    public String getAliasOfNS()  {return aliasOfns;}
    public String getMainClassName()  {return mainClassName;}
    public String getOntologyName()  {return ontologyName;}

    /**
     * @param context
     * @return the full file name (including directory) of the deninitions (model with no instances) file.
     */
    public String getOntoDefsFullfileName (WikiContext context){
        return FortunataPlugin.getURLDirectory(context) + getOntoFileName();
    }
    /**
     * @param context
     * @return the full file name (including directory) of the instances file.
     */
    public String getOntoInstancesFullfileName (WikiContext context){
        return FortunataPlugin.getURLDirectory(context) + getIndividualsFileName();
    }

    /**
     * Method to be implemented by the derived classes. You just have to add the information to
     * the indv. The model will be automatically published.
     * @param context
     * @param m The model in which the individual will be stored.
     * @param indv The individual that has been created in the model.
     */
    public abstract void createIndividual(WikiContext context, OntModel m, Individual indv);

    /**
     * Method to be implemented by the derived classes. You just have to add the information to
     * the model m, that is, the properties and relations . The model will be automatically published.
     * @param m
     */
    public abstract void fillDataModel(OntModel m);

    /**
     * Used when creating individuals.
     * @return the URI of an individual belonging to the main class. This ID must be unique for each instance.
     */
    public abstract String getInstanceUniqueURI();

    /**
     * Used to ensure that the individuals file exists. If it doesn't exist it is created and prepared to add instances.
     * @param context
     * @throws PluginException
     */
    public void createIndividualsFileIfNeeded(WikiContext context)throws PluginException{
            String ontoInstancesFullfileName = getOntoInstancesFullfileName(context);
            String ontoDefsFullfileName = getOntoDefsFullfileName(context);
            File instancesFile = new File(ontoInstancesFullfileName);
            if (instancesFile.exists() == false){
                //Reads data model from file
                Model modelData = FileManager.get().loadModel("file:" + ontoDefsFullfileName,
                                                          getURI(),
                        serializationStyle);
                //Creates an empty model
                OntModel m = ModelFactory.createOntologyModel(modelSpec, null);
                m.setNsPrefix(getAliasOfNS(), getURI());
                Ontology ontoInstances = m.createOntology(getURL() + "");  //Prefix is critical

                //Add the modelData as submodel
                m.addSubModel(modelData);

                //Stablish ontological links
                Ontology onto= m.getOntology(getURI() + getOntologyName());
                ontoInstances.addImport(onto);
                //Writes file
                try {
                    FileOutputStream fos = new FileOutputStream(ontoInstancesFullfileName);
                    m.write(fos, serializationStyle, getURI());
                    fos.close();
                }catch(IOException e){
                       throw new PluginException(getOntologyName() +"'s individuals file can not be writen");
                }
            }
    }

    /**
     * Used to ensure that the model file exists. If it doesn't exist it is created and filled.
     * @param context
     * @throws PluginException
     */
    public void createDataModelFileIfNeeded(WikiContext context)throws PluginException {
        OntModel m;
        String ontoDataFullfileName = getOntoDefsFullfileName(context);
        File fileDefs = new File(ontoDataFullfileName);
        String URI =getURI();

        if (fileDefs.exists() == false){
            m = ModelFactory.createOntologyModel(modelSpec, null);
            m.setNsPrefix(getAliasOfNS(), URI);

            //This method must be implemented by the SWApp
            fillDataModel(m);

            //Defines itself as an ontology
            m.createOntology(URI + getOntologyName());

            try {
               FileOutputStream fos = new FileOutputStream(ontoDataFullfileName);
               m.write(fos, serializationStyle, URI);
               fos.close();
            }catch(IOException e){
               log.error(getOntologyName() + "'s model file " + ontoDataFullfileName + " can not be created");
               throw new PluginException(getOntologyName() + "'s model not found");
            }
        }
    }

    /**
     * Reads both models (definitions and instances) and store them in m. The model must be created before
     * invoke this method. Once this method is invoked is your responsability to close/write the model.
     * @param m
     * @param context
     */
    public void readSWAppModel(OntModel m, WikiContext context){
        String ontoDefsFullfileName = getOntoDefsFullfileName(context);
        String ontoInstancesFullfileName = getOntoInstancesFullfileName(context);
        readSWAppModel(m, ontoDefsFullfileName, ontoInstancesFullfileName);
    }

    /**
     * Reads both models (definitions and instances) and store them in m. The model must be created before
     * invoke this method. Once this method is invoked is your responsability to close/write the model. This method do not use WikiContext.
     * @param m
     * @param ontoDefsFullfileName
     * @param ontoInstancesFullfileName
     */
    public void readSWAppModel(OntModel m, String ontoDefsFullfileName,
                                           String ontoInstancesFullfileName){

        m.setNsPrefix(getAliasOfNS(), getURI());

        //Links a http://... to a local file. So, auto import in instances file will load the definitions file
        OntDocumentManager dm = m.getDocumentManager();
        dm.addAltEntry(getURL(),
                       "file:" + ontoDefsFullfileName);
        //Reads individuals from file. Auto import will load the definitions file (see previous comment)
        Model individualsModel = FileManager.get().loadModel("file:" + ontoInstancesFullfileName,
                                                              getURI(),
                serializationStyle);
        m.add(individualsModel);
    }


    /**
     * Publishes both models (definitions and instances)
     * @param context
     * @throws PluginException
     */
    public void writeSemanticData(WikiContext context)
                         throws PluginException{
        //Ensure definitions file exists
        createDataModelFileIfNeeded(context);
        //Ensure individuals file exists
        createIndividualsFileIfNeeded(context);

        //Creates an empty model
        String ontoDefsFullfileName = getOntoDefsFullfileName(context);
        String ontoInstancesFullfileName = getOntoInstancesFullfileName(context);
        OntModel m = ModelFactory.createOntologyModel(modelSpec, null);
        m.setNsPrefix(getAliasOfNS(), getURI());

        //Reads data model from file. This is mandatory because the autoimport will not work in this case
        Model modelData = FileManager.get().loadModel("file:" + ontoDefsFullfileName,
                                                  getURI(),
                serializationStyle);
        m.addSubModel(modelData);

        //Reads individuals from file. Auto import will NOT load the definitions file in this case
        Model individualsModel = FileManager.get().loadModel("file:" + ontoInstancesFullfileName,
                                                      URI,
                serializationStyle);
        m.add(individualsModel);
        //Shows the merged model (defs + instances)

        //Creates individual
        String uriClass = URI + getMainClassName();
        OntClass c = m.getOntClass(uriClass);
        if (c == null){
            log.info("Class " + uriClass + " NOT found in defs model. This is an error that produces " + "" +
                    "rdf:Description instead of class name. Check it!");
        }
        Individual ind1 = m.createIndividual(getInstanceUniqueURI(), c);
        //This method must be implemented by the concrete SWApp
        createIndividual(context, m, ind1);

        //Writes resulting ontology (only instances!!)
        try {
            FileOutputStream fos = new FileOutputStream(ontoInstancesFullfileName);
            m.write(fos, serializationStyle, URI);
            fos.close();
        }catch(IOException e){
               log.error(getOntologyName() + "'s individuals file " + ontoInstancesFullfileName + " can not be added");
               throw new PluginException(getOntologyName() + "'s individuals can not be added");
        }
    }
}
